package com.holly.unit.auth.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.annotation.PostConstruct;
import javax.crypto.Cipher;

import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import cn.hutool.core.codec.Base64;
import lombok.extern.slf4j.Slf4j;
/**
 *  RSA目前最安全方案
 * @author pengpeng
 * 
 *由于SHA-1和RSA-1024已过时且安全性较低，因此SHA-256和RSA 2048是当前的标准。
 *SHA-256是一种非常好的安全散列算法，非常适合在证书上使用，而2048位RSA是一种很好的签名算法（注意签名与加密不同）。
 *使用带有SHA-256的2048位RSA是证书的安全签名方案
 */
@Slf4j
@Component
public class RSAUtils {
	private final static String CRYPTO_METHOD = "RSA";
	private final static String CYPHER = "RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING";
	private final static int CRYPTO_BITS = 2048;
	private static String PUB_KEY = "PUB_KEY";
	private static String PRIVATE_KEY = "PRIVATE_KEY";
	private static String CHARSET = "UTF-8";
	private static String RSA_PATH = "par_rsa.jks";

	// 私钥)
	private  PrivateKey privateKey = null;
	// 公钥
	private  PublicKey publicKey = null;
	/**
	 * 签名算法
	 */
	private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
	/*private final static int CRYPTO_BITS = 4096; This will encrypt in 4093bits, note however that is slower.*/


	/**
	 * 初始化密钥：从指定位置获取密钥对
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@PostConstruct
	private void loadKeyPair() throws IOException, ClassNotFoundException{
		ClassPathResource resource = new ClassPathResource(RSA_PATH);
		InputStream inputStream = resource .getInputStream();
		ObjectInputStream oos = new ObjectInputStream(inputStream);
		KeyPair keyPair = (KeyPair) oos.readObject();
		this.privateKey = keyPair.getPrivate();
		this.publicKey = keyPair.getPublic();

		byte[] publicKeyBytes = this.publicKey.getEncoded();
		PUB_KEY = new String(Base64.encode(publicKeyBytes));

		byte[] privateKeyBytes = this.privateKey.getEncoded();
		PRIVATE_KEY = new String(Base64.encode(privateKeyBytes));

		oos.close();
		inputStream.close();
	}
	public RSAUtils() {
		try {
			loadKeyPair();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static KeyPair getKeyPair() {
		KeyPair kp = null;
		try {
			KeyPairGenerator kpg = KeyPairGenerator.getInstance(CRYPTO_METHOD);
			kpg.initialize(CRYPTO_BITS);
			kp = kpg.generateKeyPair();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return kp;
	}

	/**
	 * 公钥加密
	 * @param clearText
	 * @return
	 */
	public static String encrypt(String clearText) {
		String encryptedBase64 = "";
		try {
			KeyFactory keyFac = KeyFactory.getInstance(CRYPTO_METHOD);
			KeySpec
			keySpec =
			new X509EncodedKeySpec(Base64.decode(PUB_KEY.trim()));
			Key key = keyFac.generatePublic(keySpec);
			final Cipher cipher = Cipher.getInstance(CYPHER);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			byte[] encryptedBytes = cipher.doFinal(clearText.getBytes(CHARSET));
			encryptedBase64 = new String(Base64.encode(encryptedBytes));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return encryptedBase64.replaceAll("(\\r|\\n)", "");
	}

	/**
	 * 使用私钥解密
	 * @param encryptedBase64
	 * @return
	 */
	public static String decrypt(String encryptedBase64) {
		String decryptedString = "";
		try {
			KeyFactory keyFac = KeyFactory.getInstance(CRYPTO_METHOD);
			KeySpec keySpec = new PKCS8EncodedKeySpec(
					Base64.decode(PRIVATE_KEY.trim()));
			Key key = keyFac.generatePrivate(keySpec);
			final Cipher cipher = Cipher.getInstance(CYPHER);
			cipher.init(Cipher.DECRYPT_MODE, key);
			byte[] encryptedBytes = Base64.decode(encryptedBase64);
			byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
			decryptedString = new String(decryptedBytes);
		} catch (Exception e) {
			return null;
		}
		return decryptedString;
	}

	/**
	 * <p>
	 * 用私钥对信息生成数字签名
	 * </p>
	 *
	 * @param data
	 *            已加密数据
	 * @return
	 * @throws Exception
	 */
	public String sign(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException,SignatureException {
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(this.privateKey);
		signature.update(data);
		return Base64.encode(signature.sign());
	}

	/**
	 * <p>
	 * 校验数字签名
	 * </p>
	 *
	 * @param data
	 *            已加密数据
	 *            公钥(BASE64编码)
	 * @param sign
	 *            数字签名
	 *
	 * @return
	 * @throws Exception
	 *
	 */
	public boolean verify(byte[] data, String sign)  throws NoSuchAlgorithmException, InvalidKeySpecException,InvalidKeyException,SignatureException {
		byte[] keyBytes = Base64.decode(PUB_KEY);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(CRYPTO_METHOD);
		PublicKey publicK = keyFactory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicK);
		signature.update(data);
		return signature.verify(Base64.decode(sign));
	}

	/**
	 * 保存生成的密钥对
	 * @param kp
	 * @throws IOException
	 */
	@SuppressWarnings("unused")
	private  void saveKeyPair(KeyPair kp) throws IOException{
		ClassPathResource resource = new ClassPathResource(RSA_PATH);
		File file = null;
		FileOutputStream fos = null;
		ObjectOutputStream oos = null;
		try {
			file = resource.getFile();
			fos = new FileOutputStream(file);
			oos = new ObjectOutputStream(fos);
			// 生成密钥
			oos.writeObject(kp);
		}catch(Exception e) {
			log.error(e.getMessage());
		} finally {
			try {
				if(null != oos){
					oos.close();
				}
				if(null != fos) {
					fos.close();
				}
			} catch (IOException e) {
				log.error("Error:" , e.getMessage());
			}
		}
	}
	/**
	 *  获取公钥
	 * @return base64公钥
	 */
	public static String getPublicKey()
	{
		return PUB_KEY;
	}
}

